﻿using System;
using System.Security.Cryptography;
using System.Text;

namespace GeneralDFS.Common
{
    public static class DFSUtils
    {
        /// <summary>
        /// 编码为UTF8，转换String为Byte数组
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        public static Byte[] StringToBytes(String str)
        {
            return Encoding.UTF8.GetBytes(str);
        }

        /// <summary>
        /// 编码为UTF8，转换Byte数组为String
        /// </summary>
        /// <param name="bytes"></param>
        /// <returns></returns>
        public static String BytesToString(Byte[] bytes)
        {
            return Encoding.UTF8.GetString(bytes);
        }

        /// <summary>
        /// 获取随机字符串
        /// </summary>
        /// <param name="length"></param>
        /// <returns></returns>
        public static String GetRandomString(int length)
        {
            var chars = new char[length];
            var random = new Random();
            for (var i = 0; i < length; i++)
            {
                chars[i] = Convert.ToChar(random.Next(0, 26) + 'a');
            }
            string s = new string(chars);
            return s;
        }

        public static UInt64 GetHash(string key)
        {
            var md5 = new SHA1CryptoServiceProvider();
            byte[] bytes = Encoding.ASCII.GetBytes(key);
            bytes = md5.ComputeHash(bytes);
            return BitConverter.ToUInt64(bytes, 0);
        }

        /// <summary>
        /// Checks whether a key is in a specified range.  Handles wraparound for cases where the start value is
        /// bigger than the end value.  Used extensively as a convenience function to determine whether or not a
        /// piece of data belongs in a given location.
        /// 
        /// Most typically, IsIDInRange is used to determine whether a key is between the local ID and the successor ID:
        ///     IsIDInRange(key, this.ID, this.Successor.ID);
        /// </summary>
        /// <param name="id">The ID to range-check.</param>
        /// <param name="start">The "low" end of the range.</param>
        /// <param name="end">The "high" end of the range.</param>
        /// <returns>TRUE if ID is in range; FALSE otherwise.</returns>
        public static bool IsIDInRange(UInt64 id, UInt64 start, UInt64 end)
        {
            if (start >= end)
            {
                // this handles the wraparound and single-node case.  for wraparound, the range includes zero, so any key
                // that is bigger than start or smaller than or equal to end is in the range.  for single-node, our nodehash
                // will equal the successor nodehash (we are our own successor), and there's no way a key can't fall in the range
                // because if range == X, then key must be either >, < or == X which will always happen!
                if (id > start || id <= end)
                {
                    return true;
                }
            }
            else
            {
                // this is the normal case where we want the key to fall between the lower bound of start and the upper bound of end
                if (id > start && id <= end)
                {
                    return true;
                }
            }
            // for all other cases we're not in range
            return false;
        }

        /// <summary>
        /// Range checks to determine if key fits in the range.  In this particular case, if the start==end of the range,
        /// we consider key to be in that range.  Handles wraparound.
        /// </summary>
        /// <param name="key">the key to range check</param>
        /// <param name="start">lower bound of the range</param>
        /// <param name="end">upper bound of the range</param>
        /// <returns>true if in the range; false if key is not in the range</returns>
        public static bool FingerInRange(UInt64 key, UInt64 start, UInt64 end)
        {
            // in this case, we are the successor of the predecessor we're looking for
            // so we return true which will mean return the farthest finger from FindClosestPrecedingFinger
            // ... this way, we can go as far around the circle as we know how to in order to find the
            // predecessor
            if (start == end)
            {
                return true;
            }
            else if (start > end)
            {
                // this handles the wraparound case - since the range includes zero, any key bigger than the start
                // or smaller than the end will be considered in the range
                if (key > start || key < end)
                {
                    return true;
                }
            }
            else
            {
                // this is the normal case - in this case, the start is the lower bound and the end is the upper bound
                // so if key falls between them, we're good
                if (key > start && key < end)
                {
                    return true;
                }
            }
            // for all other cases, we're not in the range
            return false;
        }
    }
}